package br.com.tcc.systemdx.commons.persistence.hibernate;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.List;
import java.util.Map;

import org.hibernate.Criteria;
import org.hibernate.LockOptions;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.springframework.transaction.annotation.Transactional;

import br.com.tcc.systemdx.commons.persistence.CriteriaTransformer;
import br.com.tcc.systemdx.commons.persistence.GenericDAO;

@Transactional
public abstract class GenericHibernateDAOImpl<T, ID extends Serializable> implements GenericDAO<T, ID> {

	private Class<T> persistentClass;
	
	private SessionFactory _sessionFactory;

	public GenericHibernateDAOImpl() {
	}

	public void setSessionFactory(SessionFactory s) {
		this._sessionFactory = s;
	}
	
	public SessionFactory getSessionFactory() {
		return this._sessionFactory;
	}

	protected Session _getSession() {
		if (getSessionFactory() == null)
			throw new IllegalStateException(
					"Session has not been set on DAO before usage");
		return getSessionFactory().getCurrentSession();
	}

	public Class getPersistentClass() {
		if(this.persistentClass == null) {
			this.persistentClass = (Class<T>) ((ParameterizedType) getClass()
					.getGenericSuperclass()).getActualTypeArguments()[0];
		}
		return persistentClass;
	}

	@SuppressWarnings("unchecked")
	public T findById(ID id, boolean lock) {
		if(id == null)
			return null;
		T entity;
		if (lock)
			entity = (T) _getSession().get(getPersistentClass(), id,
					LockOptions.UPGRADE);
		else
			entity = (T) _getSession().get(getPersistentClass(), id);

		return entity;
	}
	
	public T findById(ID id) {
		return findById(id, false);
	}
	
	public T findForUpdate(ID id) {
		return findById(id, true);
	}

	public List<T> findAll() {
		return findByCriteria();
	}
	

	@SuppressWarnings("unchecked")
	public List<T> findByExample(T exampleInstance, String[] excludeProperty) {
		Criteria crit = createCriteria();
		Example example = Example.create(exampleInstance);
		for (String exclude : excludeProperty) {
			example.excludeProperty(exclude);
		}
		crit.add(example);
		return crit.list();
	}

	public T makePersistent(T entity) {
		_getSession().persist(entity);
		return entity;
	}
	
	@SuppressWarnings("unchecked")
	public T updateCopy(T entity) {
		entity = (T)_getSession().merge(entity);
		return entity;
	}
	
	/**
	 * 
	 * @param entity
	 * @return
	 */
	public T refresh(T entity) {
		_getSession().refresh(entity);
		return entity;
	}

	/* (non-Javadoc)
	 * @see br.com.devcase.commons.persistence.hibernate.GenericHibernateDAO#makeTransient(T)
	 */
	public void makeTransient(T entity) {
		_getSession().delete(entity);
	}

	public void flush() {
		_getSession().flush();
	}

	public void clear() {
		_getSession().clear();
	}
	
	/**
	 * Use this inside subclasses as a convenience method.
	 */
	public List<T> findByCriteria(Criterion... criterion) {
		 return findByCriteria(null, criterion);
	}
	
	/**
	 * Use this inside subclasses as a convenience method.
	 */
	@SuppressWarnings("unchecked")
	public List<T> findByCriteria(CriteriaTransformer transformer, Criterion... criterion) {		
		Criteria crit = createFindCriteria(criterion);
		if(transformer != null) {
			crit = transformer.transformCriteria(crit);
		}
		return crit.list();
	}

	
	/* (non-Javadoc)
	 * @see br.com.devcase.commons.persistence.hibernate.GenericHibernateDAO#findByCriteria(int, int, java.lang.String, boolean, org.hibernate.criterion.Criterion)
	 */
	@SuppressWarnings("unchecked")
	public List<T> findByCriteria(int maxResults, int firstResult, String sortPropertyName, boolean ascending, Criterion... criterion) {
		Criteria crit = createCriteria(criterion);
		setPagingAndSorting(crit, maxResults, firstResult, sortPropertyName, ascending);
		crit = transformFindCriteria(crit);
		return crit.list();
	}
	
	/**
	 * 
	 * @param criterion
	 * @return
	 */
	public int countByCriteria(Criterion... criterion) {
		Criteria crit = createCriteria(criterion);
		return countByCriteria(crit);
	}
	
	protected int countByCriteria(Criteria crit) {
		crit = crit.setProjection(Projections.rowCount());
		Object o = crit.uniqueResult();
		
        if(o==null)
            return 0;
        else if(o instanceof Number) {
            return ((Number) o).intValue();
        }
        else {
            return Integer.parseInt(o.toString());
        }
	}
	
	/**
	 * Use this inside subclasses as a convenience method.
	 */
	protected Criteria createCriteria(Map<String, Object> params) {
		Criteria crit = createCriteria();
		for (Map.Entry<String, Object> param : params.entrySet()) {
			crit.add(Restrictions.eq(param.getKey(), param.getValue()));
		}
		
		return crit;
	}
	
	/**
	 * Use this inside subclasses as a convenience method.
	 */
	protected Criteria createCriteria(Criterion... criterion) {
		Criteria crit = _getSession().createCriteria(getPersistentClass());
		for (Criterion c : criterion) {
			crit.add(c);
		}
		
		return crit;
	}
	
	/**
	 * Método chamado quando usa-se o findByCriteria. Sobrescrever para adicionar
	 * ordenação, ou resulttransformer
	 * @param criteria
	 * @return
	 */
	protected Criteria transformFindCriteria(Criteria criteria) {
		return criteria;
	}
	
	protected Criteria createFindCriteria(Criterion... criterion) {
		Criteria crit = createCriteria(criterion);
		return transformFindCriteria(crit);
	}
	
	@SuppressWarnings("unchecked")
	public List<T> findByParams(int maxResults, int firstResult, String sortPropertyName, boolean ascending, Map<String, Object> params) {
		Criteria crit = createCriteria(params);
		setPagingAndSorting(crit, maxResults, firstResult, sortPropertyName, ascending);
		return crit.list(); 
	}
	
	
	
	public int countByParams(Map<String, Object> params) {
		Criteria crit = createCriteria(params);
		crit = crit.setProjection(Projections.rowCount());
		Object o = crit.uniqueResult();
		
        if(o==null)
            return 0;
        else if(o instanceof Number) {
            return ((Number) o).intValue();
        }
        else {
            return Integer.parseInt(o.toString());
        }
	}

	protected Criteria setPagingAndSorting(Criteria crit, int maxResults, int firstResult, String sortPropertyName, boolean ascending)
	{
		if(maxResults > 0)
			crit.setMaxResults(maxResults);
		if(firstResult > 0)
			crit.setFirstResult(firstResult);
		if(sortPropertyName != null && !sortPropertyName.equals("")) {
			crit.addOrder(ascending?Order.asc(sortPropertyName):Order.desc(sortPropertyName));
		}
		return crit;
	}
	
	/**
	 * 
	 * @param entity
	 */
	public void disconnect(T entity) {
		getSessionFactory().getCache().evictEntityRegion(getPersistentClass());
		_getSession().evict(entity);
	}
}
